package util.resources;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import javax.imageio.ImageIO;

/**
 * A singleton class that represents a global ResourceBundle.
 * 
 * Provides utility methods for retrieving resources of different types.
 * 
 * By making this class an single instance rather than a bunch of static
 * methods, it allows this class to be extended.
 * 
 * Changes made by Nathan: In order to use resources to define default settings
 * in a way I thought was good, I have the default resources imported every time
 * an instance of the resource manager is created. This allows me to have the
 * resources for use potentially even in constructors.
 * 
 * @author Robert C. Duvall
 */
public class ResourceManager implements Iterable<String>
{
    public static final String DEFAULT_SUBPACKAGE = "resources";
    public static final String DEFAULT_FILE = "defaults";
    public static final String DEFAULT_DELIMITER = ",";
    private static final String MISSING_RESOURCE = "Missing resource file: %s.%s";
    private static ResourceManager ourInstance;
    private static Map<String, String> myResources;

    // do not allow others to instantiate this class
    private ResourceManager()
    {
        // BUGBUG: should project be required to have resources?
        // addResourcesFromFile(DEFAULT_LANGUAGE);
    }

    /**
     * Get access to all resources for this project through the single instance
     * of this class.
     * 
     * @returns ResourceManager
     */
    public static ResourceManager getInstance()
    {
        if (ourInstance == null)
        {
            ourInstance = new ResourceManager();
        }
        if (myResources == null)
        {
            myResources = new HashMap<String, String>();
        }
        addDefaultResources();
        return ourInstance;
    }

    /**
     * Gets the keySet.
     * 
     * @returns String[]
     */
    public Collection<String> getKeySet()
    {
        return myResources.keySet();
    }

    /**
     * Returns value associated with given key as a boolean value.
     * 
     * @param key
     * @return boolean
     */
    public boolean getBoolean(String key)
    {
        return getString(key).equalsIgnoreCase("true");
    }

    /**
     * Returns value associated with given key as a integer value.
     * 
     * @param key
     * @return int
     */
    public int getInteger(String key)
    {
        return Integer.parseInt(getString(key));
    }

    /**
     * Returns value associated with given key as a double value.
     * 
     * @param key
     * @return double
     */
    public double getDouble(String key)
    {
        return Double.parseDouble(getString(key));
    }

    public Float getFloat(String key)
    {
        return Float.parseFloat(getString(key));
    }

    /**
     * Returns value associated with given key as a string value.
     * 
     * @param key
     * @return String
     */
    public String getString(String key)
    {
        return myResources.get(key);
    }

    /**
     * Returns value associated with given key as a formatted string, with the
     * given values filling in the format arguments.
     * 
     * @return String
     */
    public String getFormattedString(String key, Object... values)
    {
        return String.format(myResources.get(key), values);
    }

    /**
     * Returns value associated with given key as an array of boolean values,
     * using DEFAULT_DELIMITER as the separator between properties.
     * 
     * @return boolean[]
     */
    public boolean[] getBooleanArray(String key)
    {
        return getBooleanArray(key, DEFAULT_DELIMITER);
    }

    /**
     * Returns value associated with given key as an array of boolean values,
     * using given delimiter as the separator between properties.
     * 
     * @return boolean[]
     */
    public boolean[] getBooleanArray(String key, String delimeter)
    {
        String[] properties = getString(key).split(delimeter);
        boolean[] results = new boolean[properties.length];
        for (int k = 0; k < results.length; k++)
        {
            results[k] = properties[k].equalsIgnoreCase("true");
        }
        return results;
    }

    /**
     * Returns value associated with given key as an array of integer values,
     * using DEFAULT_DELIMITER as the separator between properties.
     * 
     * @return int[]
     */
    public int[] getIntegerArray(String key)
    {
        return getIntegerArray(key, DEFAULT_DELIMITER);
    }

    /**
     * Returns value associated with given key as an array of integer values,
     * using given delimiter as the separator between properties.
     * 
     * @return int[]
     */
    public int[] getIntegerArray(String key, String delimeter)
    {
        String[] properties = getString(key).split(delimeter);
        int[] results = new int[properties.length];
        for (int k = 0; k < results.length; k++)
        {
            results[k] = Integer.parseInt(properties[k]);
        }
        return results;
    }

    /**
     * Returns value associated with given key as an array of double values,
     * using DEFAULT_DELIMITER as the separator between properties.
     * 
     * @return double[]
     */
    public double[] getDoubleArray(String key)
    {
        return getDoubleArray(key, DEFAULT_DELIMITER);
    }

    /**
     * Returns value associated with given key as an array of double values,
     * using given delimiter as the separator between properties.
     * 
     * @return double[]
     */
    public double[] getDoubleArray(String key, String delimeter)
    {
        String[] properties = getString(key).split(delimeter);
        double[] results = new double[properties.length];
        for (int k = 0; k < results.length; k++)
        {
            results[k] = Double.parseDouble(properties[k]);
        }
        return results;
    }

    /**
     * Returns value associated with given key as an array of string values,
     * using DEFAULT_DELIMITER as the separator between properties.
     */
    public String[] getStringArray(String key)
    {
        return getStringArray(key, DEFAULT_DELIMITER);
    }

    /**
     * Returns value associated with given key as an array of string values,
     * using given delimiter as the separator between properties.
     */
    public String[] getStringArray(String key, String delimeter)
    {
        return getString(key).split(delimeter);
    }

    /**
     * Expects 3 integer values between 0 and 255, which represents RGB values.
     * 
     * @return the color
     */
    public Color getColor(String key)
    {
        int[] RGB = getIntegerArray(key);
        if (RGB.length < 3)
            throw new ResourceException(
                    key
                            + " contains less than 3 arguments, and therefore cannot be a color");
        if (RGB.length > 3)
            throw new ResourceException(
                    key
                            + " contains more than 3 arguments, and therefore cannot be a color");
        return new Color(RGB[0], RGB[1], RGB[2]);
    }

    public Dimension getDimension(String key)
    {
        int[] dim = getIntegerArray(key);
        if (dim.length < 2)
            throw new ResourceException(
                    key
                            + " contains less than 2 arguments, and therefore cannot be a dimension");
        if (dim.length > 2)
            throw new ResourceException(
                    key
                            + " contains more than 2 arguments, and therefore cannot be a dimension");
        return new Dimension(dim[0], dim[1]);
    }

    /**
     * The key should map to a filepath where the image should be located.
     * 
     * @param key
     * @return the image at the filepath
     */
    public Image getImage(String key)
    {
        String filePath = getString(key);
        try
        {
            return ImageIO.read(new File(filePath));
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        throw new ResourceException("There is no image at" + filePath);
    }

    /**
     * Iterate through all resources in this bundle.
     */
    public Iterator<String> iterator()
    {
        return myResources.keySet().iterator();
    }

    /**
     * Change resource values to those represented by this property file.
     * 
     * Given property file is assumed to be in the sub-package
     * DEFAULT_SUBPACKAGE and have the extension ".properties".
     */
    public void addResourcesFromFile(String resourcesFile)
    {
        addResourcesFromFile(resourcesFile, DEFAULT_SUBPACKAGE);
    }

    /**
     * Add resource values from given property file.
     * 
     * Note, if the property names in the given file are the same as any current
     * names, these properties will replace the current ones.
     * 
     * Given property file is assumed to be in the sub-package
     * DEFAULT_SUBPACKAGE and have the extension ".properties".
     */
    public void addResourcesFromFile(String resourcesFile, String subpackage)
    {
        getAndAddResourcesFromFile(resourcesFile, subpackage);
    }

    private static void addDefaultResources()
    {
        try
        {
            ResourceBundle tempResources = ResourceBundle
                    .getBundle("resources/defaults");
            for (String resource : tempResources.keySet())
            {
                String value = tempResources.getString(resource);
                myResources.put(resource, value);
            }
        }
        catch (MissingResourceException e)
        {
            throw new ResourceException(MISSING_RESOURCE);
        }
    }

    /**
     * Add resource values from property file relative to given object.
     * 
     * Change the resource file this bundle represents to one named after the
     * given object.
     */
    public void addResourcesFromClass(Object target)
    {
        addResourcesFromFile(getClassName(target));
    }

    public Map<String, String> getAndAddResourcesFromFile(String resourcesFile)
    {
        return getAndAddResourcesFromFile(resourcesFile, DEFAULT_SUBPACKAGE);
    }

    public Map<String, String> getAndAddResourcesFromFile(String resourcesFile, String subpackage)
    {
        try
        {
            ResourceBundle tempResources = ResourceBundle.getBundle(subpackage
                    + "." + resourcesFile);
            Map<String, String> resourcesToReturn = new HashMap<String, String>();
            for (String resource : tempResources.keySet())
            {
                String value = tempResources.getString(resource);
                resourcesToReturn.put(resource, value);
            }
            addToMyResources(resourcesToReturn);
            return resourcesToReturn;
        }
        catch (MissingResourceException e)
        {
            throw new ResourceException(MISSING_RESOURCE, subpackage,
                    resourcesFile);
        }
    }

    private void addToMyResources(Map<String, String> newResources)
    {
        for (String key : newResources.keySet())
        {
            myResources.put(key, newResources.get(key));
        }
    }

    /**
     * Get a file name based on the location of the given object.
     */
    public URL getFile(Object root, String path)
    {
        return root.getClass().getResource(path);
    }

    /**
     * Returns the name of the class to use in determining which resource file
     * to use.
     */
    protected String getClassName(Object target)
    {
        return target.getClass().getSimpleName();
    }

}
